<?php

/**
* yvSmiley - Smiley Plugin, developed for Joomla! 1.5
* @version		$Id: yvsmiley.php 0006 2008-05-27 00:00:00Z yvolk $
* @package		yvSmileyPlugin
* @copyright	2007-2008 yvolk (Yuri Volkov), http://yurivolkov.com. All rights reserved.
* @license		GNU/GPL, see LICENSE.php
* 2007-07-21 Thanks to ianmac for refactoring
*/

// no direct access
defined('_JEXEC') or die('Restricted access');

define('yvSmileyVersion', '1.03.0');
define('JPATH_SITE_YVSMILEY', dirname(__FILE__));

class plgSystemyvsmiley extends JPlugin {
	var $_initialized = false;
	var $_instanceId = 0;

	var $_debug = false;
	var $_smileyset_path = '';
	var $_smileyset_format = '';
	var $_asmiley = array ();
	//Array with sorted _asmiley for replacements 
	var $_asmileyHeader = array ();

	/**
	 * Let's support PHP4 awhile...
	 * Returns true for PHP4 
	 */
  function _PHP4() {
		if (version_compare( phpversion(), '5.0' ) < 0) {
			if (!$this->_initialized) {
			  //Something is wrong with PHP4... let's repeat the work...	
				$this->_instanceId = rand(1000, 9999);
				$this->_initialize();
			}
			return true;
		} else {
		  return false;
		}  		
  }

	/**
	 * Constructor
	 */
	function plgSystemYvsmiley(& $subject, $config) {
		parent :: __construct($subject, $config);
		$this->_instanceId = rand(1000, 9999);
		
		//global $mainframe;
		//$message = 'yvSmiley ' . $this->_instanceId . ' created.<br/>'; 
		//$mainframe->enqueueMessage($message, 'notice');
	}

	// The error handler is not used yet... maybe some time later :-)
	//	function _yvSmiley_errorHandler($errcode, $errstring, $filename=null, $lineno=null, $context=null) {
	//		global $mainframe;
	//		$mainframe->enqueueMessage('errcode=' .$errcode . '; errstring="' . $errstring , '"', 'error');
	//	}

	function onAfterRoute() {
		$this->_initialize();
	}

	// Show clickable smileys
	function onSmiley_RenderForm($id_of_target_control, & $Form, $id_of_smiley_form = '') {
		$this->_PHP4();	
		return $this->_renderForm($id_of_target_control, $Form, $id_of_smiley_form);
	}

	// Replace codes of smileys with <img> tags
	function onSmiley_RenderText(& $string_to_process) {
		$this->_PHP4();	
		$results = $this->_replaceCode($string_to_process);
	}


	function onPrepareContent(& $article, & $params, $page = 0) {
		$this->_PHP4();	
		if ($this->_getConfigValue('articletext', 0)) {
			if (isset($article->sectionid)) {
				if ($this->_articleSectionEnabled($article->sectionid)) {
					$this->_replaceCode($article->text);
				}
			}
		}
		return true;
	}

	// Do Smiley replacements on the whole page
	// Thanks to louis.landry for this idea!
	function onAfterRender() {
		$this->_PHP4();	
		if ($this->_getConfigValue('pagetext', 0)) {
			$document = & JFactory :: getDocument();
			$doctype = $document->getType();
			if ($doctype == 'html') {
				$body = JResponse :: getBody();
				if ($this->_replaceCode($body)) {
					//$body .= $doctype . '<br/>';
					JResponse :: setBody($body);
				}
			}
		}
	}

	// returns boolean Ok
	function _initialize() {
		global $mainframe;
		if ($this->_initialized) {
			JError::raiseWarning( '1' , 'yvSmiley instanceId=' . $this->_instanceId . ' was initialized already');
			return true;
		}
		//echo 'yvSmiley ' . $this->_instanceId . ' initializes...<br/>';

		$this->_initialized = true;
		$document = & JFactory :: getDocument();
		$doctype = $document->getType();

		// Only render for HTML output
		if ($doctype !== 'html') {
			return false;
		}

		$Ok = true;
		$message = '';
		$path = '';
		$url = '';
		$smileyset_baseurl = '';
		$urlBase = $mainframe->isAdmin() ? $mainframe->getSiteURL() : JURI :: base();

		$this->loadLanguage('plg_system_yvsmiley', JPATH_ADMINISTRATOR);
		//$lang = & JFactory :: getLanguage();
		//$lang->load('plg_system_yvsmiley', JPATH_ADMINISTRATOR);

		$this->_debug = $this->_getConfigValue('debug', '1');

		$this->_smileyset_format = $this->_getConfigValue('smileyset_type', 'phpbb');
		switch ($this->_smileyset_format) {
			case 'amsn' :
				$path = $this->_getConfigValue('smileyset_path', '');
				break;
			case 'csv' :
				$path = $this->_getConfigValue('smileyset_path', 'yvsmiley/phpbb/smileyset.csv');
				break;
			case 'tinymce' :
				$this->_smileyset_format = 'csv';
				$path = 'yvsmiley/smileyset_tinymce.csv';
				break;
			default :
				$this->_smileyset_format = 'csv';
				$path = 'yvsmiley/phpbb/smileyset.csv';
				break;
		}
		//echo 'smileyset_path="' . $path . '"<br />';
		$path = str_replace('/', DS, $path);
		$url = str_replace('\\', '/', $path);

		if (substr($path, 0, 8) == 'yvsmiley') {
			// path, relative from this file	  	
			$pos = strrpos($url, '/');
			if ($pos > 0) {
				$smileyset_baseurl = 'plugins/system/' . substr($url, 0, $pos +1);
			}
			$path = JPATH_SITE_YVSMILEY . DS . $path;
		} else
			if (substr($url, 0, 1) == '/') {
				// starts with '/' - this is path, relative from site root	
				$url = substr($url, 1);
				$pos = strrpos($url, '/');
				if ($pos > 0) {
					$smileyset_baseurl = substr($url, 0, $pos +1);
				}
				$path = JPATH_SITE . $path;
			}
		if (!file_exists($path)) {
			$Ok = false;
			$message .= 'The smiley set file doesn\'t exist="' . $path . '"';
		} else {
			$this->_smileyset_path = $path;
			$Ok = $this->_readSmileySet($message, $smileyset_baseurl);
		}

		if ($this->_getConfigValue('usedefaultcss', '1')) {
			$document->addStyleSheet($urlBase . 'plugins/system/yvsmiley/default.css', 'text/css');
		}
		$document->addScript($urlBase . 'plugins/system/yvsmiley/default.js', 'text/javascript');

		if ($Ok && $this->_debug) {
			// test loaded smileys
			$ControlID = 'document.forms.yvSmileyDebugForm' . $this->_instanceId . '.fulltext';
			$strForm = '';
			$strForm .= 'Smiley set file="' . $this->_smileyset_path . '"';
			$strForm .= '; instanceId=' . $this->_instanceId ;
			$this->onSmiley_RenderForm($ControlID, $strForm, 'yvSmileyForm' . $this->_instanceId);
			$strForm .= '<form id=\'yvSmileyDebugForm' . $this->_instanceId . '\' ><textarea id=\'fulltext\' rows=\'3\' cols=\'80\' >something</textarea></form>';
			$strForm .= $this->_textSignature();
			$mainframe->enqueueMessage($strForm, 'notice');
		}
		if (strlen($message) > 0) {
			$message .= 'Smiley set file="' . $this->_smileyset_path . '"';
			$message .= $this->_textSignature();
			$mainframe->enqueueMessage($message, ($Ok ? 'notice' : 'error'));
		}
		return ($Ok);
	}

	function _readSmileySet(& $message, $smileyset_baseurl = '') {
		$Ok = true;
		switch ($this->_smileyset_format) {
			case 'amsn' :
				$Ok = $this->_readSmileySet_aMSN($message, $smileyset_baseurl);
				break;
			default :
				$Ok = $this->_readSmileySet_CSV($message, $smileyset_baseurl);
				break;
		}
		if ($Ok) {
			// Sort Smiley set by length of code descending - for replacement
			// so ':oops:' won't be replaced by <image for :o>ops:
			for ($i1 = 0; $i1 < count($this->_asmiley); $i1++) {
				$this->_asmileyHeader[$i1] = $i1;
			}
			for ($i1 = 0; $i1 < (count($this->_asmiley) - 1); $i1++) {
				for ($i2 = count($this->_asmiley) - 1; $i2 > $i1; $i2--) {
					if (strlen($this->_asmiley[$this->_asmileyHeader[$i2 -1]][0]) < strlen($this->_asmiley[$this->_asmileyHeader[$i2]][0])) {
						//swap
						$i3 = $this->_asmileyHeader[$i2 -1];
						$this->_asmileyHeader[$i2 -1] = $this->_asmileyHeader[$i2];
						$this->_asmileyHeader[$i2] = $i3;
					}
				}
			}
		}

		return $Ok;
	}

	// Read 'csv' file
	// $message - error message
	function _readSmileySet_CSV(& $message, $smileyset_baseurl = '') {
		global $mainframe;

		$Ok = true;
		$urlBase = $mainframe->isAdmin() ? $mainframe->getSiteURL() : JURI :: base();

		$ind1 = 0;
		$indLine = 0;
		$handle = fopen($this->_smileyset_path, 'r');
		// read file line by line
		while (($data = fgetcsv($handle, 1000, ';')) !== false) {
			$indLine += 1;		
			$skip = false;
			$nfields = count($data);
			if ($nfields == 3) {
				$code = trim($data[0]);
				if (strlen($code) > 0) {
					$alt = JText :: _($data[1]);
					$src = trim($data[2]);
					if (strlen($src) == 0) {
						$skip = true;
					} else {
						$posColon = strpos($src, ':');
						if (!($posColon > 0)) {
							// relative path
							if ((substr($src, 0, 1) == '/')) {
								$src = $urlBase . substr($src, 1);
							} else {
								// relative from $smileyset_path 			  	
								$src = $urlBase . $smileyset_baseurl . $src;
							}
						}
						//echo '<img src="' . $src . '" alt="' . $alt . '">';

						$this->_asmiley[$ind1][0] = $code;
						$this->_asmiley[$ind1][1] = $alt;
						$this->_asmiley[$ind1][2] = $src;
					}
				} else {
					$skip = true;
					$Ok = false;
					$message .= 'No code found in line ' . ($indLine) . ':"' . print_r($data, true) . '"<br />';
				}
			} else {
				$skip = true;
				$Ok = false;
				$message .= 'Wrong number of fields ' . $nfields . ' in line ' . ($indLine) . ':"' . print_r($data, true) . '"<br />';
			}

			if (!$skip) {
				$ind1++;
			}
		} // while
		fclose($handle);
		return $Ok;
	}

	// Read 'aMSN' file, http://www.amsn-project.net/skins.php
	// $message - error message
	function _readSmileySet_aMSN(& $message, $smileyset_baseurl = '') {
		global $mainframe;

		$Ok = true;
		// This doesn't prevent from fatal error reports...
		//$errorHandler_prev = set_error_handler('_yvSmiley_errorHandler');
		$urlBase = $mainframe->isAdmin() ? $mainframe->getSiteURL() : JURI :: base();
		//echo 'urlBase="' . $urlBase . '"; smileyset_baseurl="' . $smileyset_baseurl . '"<br>';

		//jimport('joomla.factory');
		jimport('joomla.utilities.simplexml');
		
		$xml = new JSimpleXML();
		$xml->loadFile($this->_smileyset_path);
		if (!$xml->document) {
			$message .= 'Error loading aMSN SmileSet file<br />';
			$Ok = false;
		}
		if ($Ok) {
			$smileys = $xml->document->smileys[0];
			if (!$smileys) {
				$message .= '"smileys" element not found in aMSN SmileSet file ?? Maybe this is wrong XML file?<br />';
				$Ok = false;
			}
		}
		if ($Ok) {
			$ind1 = 0;
			foreach ($smileys->children() as $child) {
				if ($child->name() == 'emoticon') {
					//print_r($child);	

					$alt = trim($child->name[0]->data());
					if (isset($child->file)) {
						$src = trim($child->file[0]->data());
					} else {
						if ($this->_debug) {
							$msg = 'yvSmiley: no \'file\' for emoticon with name="' . $alt .'"<br />';
							$msg .= print_r($child, true) .'"<br />';	
							
							$mainframe->enqueueMessage($msg, 'notice');
						}
						continue;
					}
					
					if (strlen($src) > 0) {
						$posColon = strpos($src, ':');
						if (!($posColon > 0)) {
							// relative path
							if ((substr($src, 0, 1) == '/')) {
								$src = $urlBase . substr($src, 1);
							} else {
								// relative from $smileyset_path 			  	
								$src = $urlBase . $smileyset_baseurl . 'smileys/' . $src;
							}
						}
						//echo '<img src="' . $src . '" alt="' . $alt . '">';
						foreach ($child->children() as $child2) {
							$skip = false;
							//$message .= $child2->name() . ' -> ' . $child2->data() . '<br/';
							switch ($child2->name()) {
								case 'text' :
									$code = trim($child2->data());
									if (substr($code, 0, 1) == '"') {
										$code = substr($code, 1, strlen($code) - 2);
									}
									if (strpos(' ' . $code, '"') > 0) {
										// I couldn't find out, how to put '"' there :-( 	
										$skip = true;
										//$code = str_replace('"','"""', $code);
									}
									if (!$skip) {
										$this->_asmiley[$ind1][0] = $code;
										$this->_asmiley[$ind1][1] = $alt;
										$this->_asmiley[$ind1][2] = $src;
									}
									break;
								default :
									$skip = true;
									break;
							}
							if (!$skip) {
								$ind1++;
							}
						} //	foreach $child
					}
				}
			}
		}
		//set_error_handler($errorHandler_prev);
		return $Ok;
	}

	// Show clickable smileys
	// $ControlID - id of target control
	// $Form - string, to which the Form code is appended
	function _renderForm($ControlID, & $Form, $FormID) {
		$strOut = '';
		$strOut .= '<div class=\'yvSmileyForm\'';
		if (!empty($FormID)) {
    	$strOut .= ' id=\'' . $FormID . '\'';
		} 
    $strOut .= '>';
		$src_prev = '';
		foreach ($this->_asmiley as $row) {
			$code = $row[0];
			$code = str_replace('\'', '\\\'', $code);
			$alt = $row[1];
			$src = $row[2];
			;
			if (strcmp($src_prev, $src) != 0) {
				$strOut .= '<a href="javascript:void(0);" onclick="yvSmileyReplaceText(' . $ControlID . ', \'' . $code . '\');">';
				$strOut .= '<img src="' . $src . '" alt="' . $alt . '" />';
				$strOut .= '</a>';
				$src_prev = $src;
			}
		}
		$strOut .= '</div>';
		$Form .= $strOut;
		return true;
	}

	/* Replace codes of smileys with <img> tags
	 * This code is taken from yvBBCode 
	 *  $text - String to process */
	function _replaceCode(& $text) {
		$debug = false;
		$message = '';	

		if (count($this->_asmileyHeader) == 0) {
			if ($debug) {
				$message .= 'No Smileys in the set<br />';
				if (!empty($message)) {
					$text .= $message;
				}										
			}	
			return true;
		}
	
		// Don't replace codes or something else in square brackes :-)
		//   inside these elements (tags):
		$tagsToExclude = array('head', 'title', 'textarea', 'input', 'script', 'select');

		$text = '*' . $text; // for strpos
		$strOut = '';
		$start1 = 0; // start offset
		$end1 = strlen($text); // end offset
		
		while ($start1 < $end1) {
			if ($debug) {
				$message .= ' start1=' . $start1 . ' end1=' . $end1 . '; ';
			}	
			//find nearest tag to exclude
			$tag3 = '';
			$start3 = $end1; 
			$end3 = $end1;
			foreach( $tagsToExclude as $tagToExclude) {
				$start4 = strpos( $text, '<' . $tagToExclude , $start1);
				if	(($start4 > 0) && ($start4 < $start3)) {
					$tag3 = $tagToExclude;
					$start3 = $start4;
				}
			} 
			if ($start3 < $end1) {
				if ($debug) {
					$message .= 'tag=' . $tag3 . ' start=' . ($start3 + strlen($tag3)) . '; ';
				}	
				//find end of element
				// 1. End of this tag
				$end4 = strpos( $text, '>', ($start3 + strlen($tag3)));
				if	(($end4 > 0) && ($end4 < $end3) && (substr($text, $end4 - 1, 1) == '/')) {
					if ($debug) {
						$message .= 'EndOfThisTag=' . $end4;
					}	
					$end3 = $end4;
				} else {
					// 2. Or closing element
					$end4 = strpos( $text, '</' . $tag3 , ($start3 + strlen($tag3)));
					if ($debug) {
						$message .= 'ClosingTag=' . $end4;
					}	
					if	(($end4 > 0) && ($end4 < $end3)) {
						$end4 = strpos($text, '>', $end4 +1);
					}
					if ($debug) {
						$message .= ' end4=' . $end4;
					}	
					if	(($end4 > 0) && ($end4 < $end3)) {
						$end3 = $end4 + 1;
					}
				}
			}
			if ($debug) {
				$message .= ' start3=' . $start3 . ' end3=' . $end3;
			}	
			
			if ($start3 > $start1) {
				// replace before tag to exclude	
				$str1 = substr($text, $start1, $start3 - $start1);
				$this->_parse($str1);
				$strOut .= $str1; 				
			}
			if ($start3 < $end1) {
				// don't replace here	
				$str1 = substr($text, $start3, $end3 - $start3);
				$strOut .= $str1; 				
			}
			
			$start1 = $end3;
			if ($debug) {
				$message .= '<br />';
			}	
		};
		
		// remove first character, that was added;
		$text = substr($strOut, 1);  

		if ($debug) {
			if (!empty($message)) {
				$text .= $message;
			}										
		}	
		return true;
	}

	// Here we actually replace smiley codes with images
	// Replacements are being made in the whole input string
	function _parse(& $text) {
		if (count($this->_asmileyHeader) == 0) {
			return true;
		}
		
		$offset = 0;
		do {
			$pos1 = $offset - 1;
			$code = '';
			$alt = '';
			$src = '';
			
			// Find nearest code
			for ($i1 = 0; $i1 < count($this->_asmiley); $i1++) {
				$row = $this->_asmiley[$this->_asmileyHeader[$i1]];
				$code2 = $row[0];
				$offset2 = $offset;
				do {
					$blnSkip = false;
					$pos2 = strpos($text, $code2, $offset2);
				  if (($pos2 === false) || (($pos2 >= $pos1) && ($pos1 >= $offset))) { 
				  	break; 
				  }
				  
					$len2 = strlen($code2);				
					// Simple check to ensure we're not inside the tag
					$posClose = strpos($text, '>', $pos2);
					if (!($posClose === false)) {
						// '+ $len2' is to skip '<' inside the smiley code					
						$posOpen = strpos($text, '<', $pos2 + $len2);
						if (($posOpen > $posClose)) {
							// We're inside the tag, so don't replace code
							$blnSkip = true;
							$offset2 = $posClose + 1;
						}
						if (!$blnSkip) {
							$str1 = substr($text, 0, $pos2);	
							$posOpenBefore = strrpos($str1, '<');
							if (!($posOpenBefore === false)) {
								$posCloseBefore = strrpos($str1, '>');
								if ($posOpenBefore > $posCloseBefore) {
									// We're inside the tag, so don't replace code
									$blnSkip = true;
									$offset2 = $posClose + 1;
								}
							}
						}
					}
					
					if (!$blnSkip) {
						$pos1 = $pos2;	
						$code = $code2;
						$alt = $row[1];
						$src = $row[2];
					}
				} while($blnSkip);
			}
			if ($pos1 < $offset) { break; }
			
			$len1 = strlen($code);
			$str2 = '<img src="' . $src . '" alt="' . $alt . '" class="yvSmiley"/>';
			$offset = $pos1 + strlen($str2);
			$text = substr($text, 0, $pos1) . $str2 . substr($text, $pos1 + $len1);
		} while (1);

		return true;
	}

	// Is Section with this ID enabled for some operation...  
	function _articleSectionEnabled($ArticleSectionID) {
		static $sEnabled = null;
		static $aSectionID = array ();
		static $blnExclude = false;
		if ($sEnabled == null) {
			$articlesectionids = trim($this->_getConfigValue('articlesectionids', ''));
			$sEnabled = (strlen($articlesectionids) == 0);
			if (!$sEnabled) {
				$aSectionID = explode(",", $articlesectionids);
				$blnExclude = (boolean) $this->_getConfigValue('articlesectionids_exclude', '0');
			}
		}
		$Enabled = $sEnabled;
		if (!$sEnabled) {
			$blnFound = false;
			foreach ($aSectionID as $sectionid1) {
				if ((int) $sectionid1 == $ArticleSectionID) {
					$blnFound = true;
					break;
				}
			}

			if ($blnFound) {
				$Enabled = !$blnExclude;
			} else {
				$Enabled = $blnExclude;
			}
		}
		return $Enabled;
	}

	// returns value of the Extension parameter
	function _getConfigValue($paramName = '', $default = '') {
		$value = $default;

		switch ($paramName) {
			case 'access' :
				if ($this->params) {
					$value = 0;
				} else {
					// if Plugin is not loaded, then Access is denied
					$value = 0; //999; 
				}
				break;
			default :
				if ($this->params) {
					$value = $this->params->get($paramName, $default);
				}
		}
		//echo '_getConfigValue param="' . $paramName . '", value="' . $value . '"<br/>';

		return $value;
	}

	// Signature of this Extension
	function _textSignature() {
		$message = '<br/>-- <br/>' .
		'<a href="http://yurivolkov.com/Joomla/yvSmiley/index_en.html" target="_blank">' .
		'yvSmiley extension</a>, version="' .
		yvSmileyVersion . '"';
		return $message;
	}
}
?>
